package com.olclass.web.support;


import org.apache.commons.lang.StringUtils;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import javax.persistence.criteria.CriteriaBuilder.In;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class QuerySpecification<T> implements Specification<T> {

	String filterParam;

	public QuerySpecification(String filterParam) {

		this.filterParam = filterParam;
	}

	@Override
	public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
		final List<QueryFilterParam> queryParamFilters = QueryParamUtil.parseQueryFilterParams(filterParam);
		Predicate restrictions = buildPredicate(queryParamFilters, root,query, cb);
		return restrictions;
	}
	
	private Predicate buildPredicate(final List<QueryFilterParam> queryParamFilters, Root<?> root, CriteriaQuery<?> query,
                                     CriteriaBuilder cb) {
		Predicate restrictions = null;
		Predicate preD = null;
		
		/*List<Predicate> predicates = new ArrayList();
		JoinModel linkModel = JoinModel.AND;*/
		int size = queryParamFilters.size();
		for (int i = 0; i < size; i++) {
			QueryFilterParam queryParamFilter = queryParamFilters.get(i);
			if(queryParamFilter.isNest() && queryParamFilter.getChildrens().size() > 0 ){
				preD = buildPredicate(queryParamFilter.getChildrens(), root,query, cb);
			}else{
				preD = predicateByQueryParam(queryParamFilter,root,cb);
			}
			
			/*if (queryParamFilter.getJoinModel() == JoinModel.AND) {
				preD = cb.and(preD);
			}else if (queryParamFilter.getJoinModel() == JoinModel.OR) {
				preD = cb.or(preD);
			}	*/
			
			
			
			/*if (i == 0) {
				linkModel = queryParamFilter.getJoinModel();
			}*/
	
			// 加入连接条件
			if (i == 0) {
				restrictions = preD;
			} else {

				if (queryParamFilter.getJoinModel() == QueryFilterParam.JoinModel.AND) {
					restrictions = cb.and(restrictions,preD);
				} else if (queryParamFilter.getJoinModel() == QueryFilterParam.JoinModel.OR) {
					restrictions = cb.or(restrictions,preD);
				}

			}
		}
          
		return restrictions;
	}
	
	private Predicate predicateByQueryParam(QueryFilterParam queryParamFilter, Root<?> root,
                                            CriteriaBuilder cb){
		Predicate preD = null;
		QueryFilterParam.OperationModel opModel = queryParamFilter.getOperationMode();
		Map<String, Join<Object,Object>>joinMap = new HashMap<String, Join<Object,Object>>();
		String propName = queryParamFilter.getParamName();
		String[] tmp = 	StringUtils.split(propName, '.');
	    
		switch (queryParamFilter.getFieldType()) {
		
			case DateField:
				Path<Date> exp;
				if (tmp.length == 2) {
					if(!joinMap.containsKey(tmp[0])){
						joinMap.put(tmp[0], root.join(tmp[0],queryParamFilter.getJoinType()));
					}
					exp = joinMap.get(tmp[0]).<Date> get(tmp[1]);
				} else {
					exp = root.<Date> get(propName);
				}
	
				switch (opModel) {
				case EQ:
					if(queryParamFilter.getParamValue() == null || queryParamFilter.getParamValue().toString().equalsIgnoreCase("null")){
						preD = cb.isNull(exp);
					}else{
						long timeInt = Long.parseLong(queryParamFilter.getParamValue().toString());
						Date queryDate = new Date(timeInt);
						preD = cb.equal(exp, queryDate);
						
					}
					break;
				case NE:
					if(queryParamFilter.getParamValue() == null || queryParamFilter.getParamValue().toString().equalsIgnoreCase("null")){
						preD = cb.isNotNull(root.get(propName));
					}else{
						long timeInt = Long.parseLong(queryParamFilter.getParamValue().toString());
						Date queryDate = new Date(timeInt);
						preD = cb.notEqual(exp, queryDate);
					}
					break;
                case GT:
                	long timeInt = Long.parseLong(queryParamFilter.getParamValue().toString());
					Date queryDate = new Date(timeInt);
					preD = cb.greaterThan(exp, queryDate);
					break;
				case LT:
					timeInt = Long.parseLong(queryParamFilter.getParamValue().toString());
				    queryDate = new Date(timeInt);
					preD = cb.lessThan(exp, queryDate);
					break;
				case GE:
					timeInt = Long.parseLong(queryParamFilter.getParamValue().toString());
					queryDate = new Date(timeInt);
					preD = cb.greaterThanOrEqualTo(exp, queryDate);
					break;
				case LE:				
					timeInt = Long.parseLong(queryParamFilter.getParamValue().toString());
					queryDate = new Date(timeInt);
					preD = cb.lessThanOrEqualTo(exp, queryDate);
				case BTW:
					String[] timePair = queryParamFilter.getParamValue().toString().split("&&");
					long timelo = Long.parseLong(timePair[0]);
					long timego = Long.parseLong(timePair[1]);
	
					Date queryDatelo = new Date(timelo);
					Date queryDatego = new Date(timego);
		
					preD = cb.between(exp, queryDatelo, queryDatego);
					break;
				default:
					break;
				}
				break;
			case NumberField:
				Path<Number> exp2;
				if (tmp.length == 2) {
					if(!joinMap.containsKey(tmp[0])){
						joinMap.put(tmp[0], root.join(tmp[0],queryParamFilter.getJoinType()));
					}
					exp2 = joinMap.get(tmp[0]).<Number> get(tmp[1]);
				} else {
					exp2 = root.<Number> get(propName);
				}
	
				switch (opModel) {
				case EQ:
					if(queryParamFilter.getParamValue() == null || queryParamFilter.getParamValue().toString().equalsIgnoreCase("null")){
						preD = cb.isNull(exp2);
					}else{
					    preD = cb.equal(exp2, Long.valueOf(queryParamFilter.getParamValue().toString()));
					}
					break;
				case NE:
					if(queryParamFilter.getParamValue() == null || queryParamFilter.getParamValue().toString().equalsIgnoreCase("null")){
						preD = cb.isNotNull(root.get(propName));
					}else{
					   preD = cb.notEqual(exp2, Long.valueOf(queryParamFilter.getParamValue().toString()));
					}
					break;
				case GE:
					preD = cb.ge(exp2, Long.valueOf(queryParamFilter.getParamValue().toString()));
					break;
				case LE:
					preD = cb.le(exp2, Long.valueOf(queryParamFilter.getParamValue().toString()));
					break;
				case GT:
					preD = cb.gt(exp2, Long.valueOf(queryParamFilter.getParamValue().toString()));
					break;
				case LT:
					preD = cb.lt(exp2, Long.valueOf(queryParamFilter.getParamValue().toString()));
					break;
				case IN:
					String[] numPair = queryParamFilter.getParamValue().toString().split(",");
					if(numPair.length>0){
						In<Number> in = cb.in(exp2);
						for(int k=0,length=numPair.length;k<length;k++){
							in.value(Long.valueOf(numPair[k]));
						}
						preD = in;
					}
					break;
				case NOTIN:
					String[] numPair2 = queryParamFilter.getParamValue().toString().split(",");
					if(numPair2.length>0){
						In<Number> in = cb.in(exp2);
						for(int k=0,length=numPair2.length;k<length;k++){
							in.value(Long.valueOf(numPair2[k]));
						}
						preD = cb.not(in);
					}
					break;	
				default:
					break;
				}
				break;
			case StringField:
				Path<String> exp3;
				if (tmp.length == 2) {
					if(!joinMap.containsKey(tmp[0])){
						joinMap.put(tmp[0], root.join(tmp[0],queryParamFilter.getJoinType()));
					}
					exp3 = joinMap.get(tmp[0]).<String> get(tmp[1]);
				} else {
					exp3 = root.<String> get(propName);
				}
	
				switch (opModel) {
				case EQ:
					if(queryParamFilter.getParamValue() == null || queryParamFilter.getParamValue().toString().equalsIgnoreCase("null")){
						preD = cb.isNull(exp3);
					}else{
						preD = cb.equal(exp3, queryParamFilter.getParamValue());	
					}
					
					break;
				case NE:
					if(queryParamFilter.getParamValue() == null || queryParamFilter.getParamValue().toString().equalsIgnoreCase("null")){
						preD = cb.isNotNull(root.get(propName));
					}else{
					    preD = cb.notEqual(exp3, queryParamFilter.getParamValue());
					}
					break;
				case GT:
					preD = cb.greaterThan(exp3, queryParamFilter.getParamValue().toString());
					break;
				case LT:
					preD = cb.lessThan(exp3, queryParamFilter.getParamValue().toString());
					break;
				case GE:
					preD = cb.greaterThanOrEqualTo(exp3, queryParamFilter.getParamValue().toString());
					break;
				case LE:				
					preD = cb.lessThanOrEqualTo(exp3, queryParamFilter.getParamValue().toString());
					break;
				case LK:
					preD = cb.like(exp3, "%" + queryParamFilter.getParamValue().toString() + "%");
					break;
				case LFK:
					preD = cb.like(exp3, queryParamFilter.getParamValue().toString() + "%");
					break;
				case RHK:
					preD = cb.like(exp3, "%" + queryParamFilter.getParamValue().toString());
					break;
				case NLK:
					preD = cb.notLike(exp3, "%" + queryParamFilter.getParamValue().toString() + "%");
					break;
				case IN:
					String[] strPair = queryParamFilter.getParamValue().toString().split(",");
					if(strPair.length>0){
						In<String> in = cb.in(exp3);
						for(int k=0,length=strPair.length;k<length;k++){
							in.value(strPair[k]);
						}
						preD = in;
					}
					break;
				case NOTIN:
					String[] strPair2 = queryParamFilter.getParamValue().toString().split(",");
					if(strPair2.length>0){
						In<String> in = cb.in(exp3);
						for(int k=0,length=strPair2.length;k<length;k++){
							in.value(strPair2[k]);
						}
						preD = cb.not(in);
					}
					break;	
				default:
					break;
				}
				break;
			default:
				break;
		}
		
		return preD;
		
	}
	
	private  void printQueryFilterParam(QueryFilterParam queryParam ){
		if(queryParam.isNest()){
			System.out.println(queryParam.getExpression());
			List<QueryFilterParam>  queryParams = queryParam.getChildrens();
			int index = 1;
			for(QueryFilterParam subQueryParam:queryParams){
				System.out.println("-------------------1" + index + "---------------------");
				printQueryFilterParam(subQueryParam);
				index++;
			}
		}else{
			System.out.println(queryParam.getExpression() + "********" +  queryParam.getParamName() + ";" + queryParam.getParamValue() );
		}
	}
}
